package fr.cephb.operonj.locustree;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sleepycat.bind.tuple.IntegerBinding;
import com.sleepycat.bind.tuple.LongBinding;
import com.sleepycat.bind.tuple.StringBinding;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.bind.tuple.TupleInput;
import com.sleepycat.bind.tuple.TupleOutput;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

import fr.cephb.operonj.core.je.DefaultDuplicateDB;
import fr.cephb.operonj.core.je.DefaultPrimaryDB;
import fr.cephb.operonj.core.je.DuplicateDB;
import fr.cephb.operonj.core.je.PrimaryDB;
import fr.cephb.operonj.core.je.Walker;

/**
 * A implementation of the LocusTree
 * storing data using BerkeleyDB
 * Mostly inspired from Jan Aerts' work
 *   http://saaientist.blogspot.com/
 *   
 * @author Pierre Lindenbaum
 *
 */
public class LocusTreeDB
	{
	private static final Logger LOG= Logger.getLogger("fr.cephb.operonj.locustree");
	
	private int start_resolution=100000000;
	private int end_resolution=100;
	private int child_per_node=10;
	private long node_id_generator=0L;
	
	private File dbHome=new File(System.getProperty("java.io.tmpdir"),"bdb");
	private Environment environment=null;
	private Transaction txn=null;
	private PrimaryDB<Long, LocusNode> id2locusNode;
	private PrimaryDB<String,Integer> featuresTypes;
	private PrimaryDB<String, ChromInfo> name2chromosome;
	private DuplicateDB<TypeName, Position> name2mappedObject;
	private int feature_type_generator=0;
	private int chromosome_generator=0;
	

	
	
	/**
	 * TypeName
	 *
	 */
	private static class TypeName
		{
		private int type_id;
		private String name;
		TypeName(int type_id,String name)
			{
			this.type_id=type_id;
			this.name=name;
			if(this.name==null) throw new NullPointerException();
			}
		@Override
		public int hashCode()
			{
			final int prime = 31;
			int result = 1;
			result = prime * result + name.hashCode();
			result = prime * result + type_id;
			return result;
			}
		@Override
		public boolean equals(Object obj)
			{
			if (this == obj) return true;
			if (obj == null) return false;
			if (!(obj instanceof TypeName)) return false;
			TypeName other = (TypeName) obj;
			if (type_id != other.type_id) return false;
			return (name.equals(other.name)) ;
			}
		@Override
		public String toString()
			{
			return "{type:"+type_id+",name:\""+name+"\"}";
			}
		}
	
	/**
	 * TypeNameBinding
	 *
	 */
	private static class TypeNameBinding
		extends TupleBinding<TypeName>
		{
		@Override
		public TypeName entryToObject(TupleInput input)
			{
			return new TypeName(input.readInt(),input.readString());
			}
		@Override
		public void objectToEntry(TypeName object, TupleOutput output)
			{
			output.writeInt(object.type_id);
			output.writeString(object.name);
			}
		}
	
	/**
	 * Position chromosome/start/end
	 *
	 */
	public static class Position
		{
		int chromosome_id;
		int start;
		int end;
		Position(int chromosome_id,int start,int end)
			{
			this.chromosome_id=chromosome_id;
			this.start=start;
			this.end=end;
			}
		
		
		@Override
		public int hashCode()
			{
			final int prime = 31;
			int result = 1;
			result = prime * result + chromosome_id;
			result = prime * result + end;
			result = prime * result + start;
			return result;
			}
		
		@Override
		public boolean equals(Object obj)
			{
			if (this == obj) return true;
			if (obj == null) return false;
			if (getClass() != obj.getClass()) return false;
			Position other = (Position) obj;
			if (chromosome_id != other.chromosome_id) return false;
			if (end != other.end) return false;
			if (start != other.start) return false;
			return true;
			}


		@Override
		public String toString()
			{
			return "chromId."+chromosome_id+":"+start+"-"+end;
			}
		}
	
	/**
	 * PositionBinding
	 * @author pierre
	 *
	 */
	private static class PositionBinding
		extends TupleBinding<Position>
		{
		@Override
		public Position entryToObject(TupleInput input)
			{
			return new Position(
					input.readInt(),
					input.readInt(),
					input.readInt()
					);
			}
		@Override
		public void objectToEntry(Position object, TupleOutput output)
			{
			output.writeInt(object.chromosome_id);
			output.writeInt(object.start);
			output.writeInt(object.end);
			}
		}
	
	private static class ChromInfo
		{
		String name;
		int id;
		int length=0;
		long root_node_id=-1L;
		}
	
	/** 
	 * ChromInfoBinding
	 *
	 */
	private static class ChromInfoBinding
	extends TupleBinding<ChromInfo>
		{
		@Override
		public ChromInfo entryToObject(TupleInput input)
			{
			ChromInfo ci= new ChromInfo();
			ci.name=input.readString();
			ci.id=input.readInt();
			ci.length=input.readInt();
			ci.root_node_id=input.readLong();
			return ci;
			}
		
		@Override
		public void objectToEntry(ChromInfo ci, TupleOutput output)
			{
			output.writeString(ci.name);
			output.writeInt(ci.id);
			output.writeInt(ci.length);
			output.writeLong(ci.root_node_id);
			}
		}
	
	
	public static class MappedObject
		implements Cloneable
		{
		int typeId;
		int chromosome_id;
		String name;
		int start;
		int end;
		
		public MappedObject()
			{
			
			}
		public MappedObject(TypeName type,Position position)
			{
			this.typeId=type.type_id;
			this.name=type.name;
			this.chromosome_id=position.chromosome_id;
			this.start=position.start;
			this.end=position.end;
			}
		
		MappedObject(MappedObject cp)
			{
			typeId=cp.typeId;
			chromosome_id=cp.chromosome_id;
			name=cp.name;
			start=cp.start;
			end=cp.end;
			}
		
		@Override
		public int hashCode()
			{
			final int prime = 31;
			int result = 1;
			result = prime * result + chromosome_id;
			result = prime * result + end;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			result = prime * result + start;
			result = prime * result + typeId;
			return result;
			}
		@Override
		public boolean equals(Object obj)
			{
			if (this == obj) return true;
			if (obj == null) return false;
			if (getClass() != obj.getClass()) return false;
			MappedObject other = (MappedObject) obj;
			if (chromosome_id != other.chromosome_id) return false;
			if (end != other.end) return false;
			if (start != other.start) return false;
			if (typeId != other.typeId) return false;
			if (!name.equals(other.name)) return false;
			return true;
			}
		
		public int size()
			{
			return end-start;
			}
		
		@Override
		public String toString()
			{
			return "\""+ name+"\"["+typeId+"]chr"+chromosome_id+":"+start+"-"+end+" size:"+size();
			}
		@Override
		protected Object clone()
			{
			return new MappedObject(this);
			}
		}
	
	private class ContentByType
		{
		//int typeId=0;
		int count=0;
		Set<String> features=null;
		}
	
	private class LocusNode
		{
		long id;
		int chromosome_id;
		int start;
		int length;
		int level=0;
		List<Long> children_ids=null;
		Map<Integer,ContentByType> type2content=null;
		
		LocusNode()
			{
			}
		
		@Override
		public String toString()
			{
			StringBuilder b= new StringBuilder(
				"{\nid:"+id+",\nchrom:"+chromosome_id+
				",\nstart:"+start+
				",\nlength:"+length+
				",\nlevel:"+level
				);
			if(children_ids==null || children_ids.isEmpty())
				{
				b.append(",\nchil-id:null");
				}
			else
				{
				b.append(",\nchil-id:[");
				for(int i=0;i<children_ids.size();++i )
					{
					if(i!=0) b.append(",");
					b.append(children_ids.get(i));
					}
				b.append("]");
				}
			if(type2content==null )
				{
				b.append(",\nfeatures:{}");
				}
			else
				{
				b.append(",\nfeatures:[");
				boolean first=true;
				for(Integer type:type2content.keySet())
					{
					b.append("\""+type+"\":{");
					ContentByType cbt= this.type2content.get(type);
					if(cbt.features!=null)
						{
						for(String m: cbt.features)
							{
							if(!first) b.append(",");
							first=false;
							b.append(m.toString());
							}
						}
					b.append("}");
					}
				b.append("]");
				}
			
			b.append("}");
			return b.toString();
			}
		
		public boolean isRoot()
			{
			return level==0;
			}
		
		public int getResolution()
			{
			return isRoot()?LocusTreeDB.this.start_resolution:this.length;
			}
		
		public int getChildResolution()
			{
			return getResolution()/LocusTreeDB.this.child_per_node;
			}
		
	
		public void add(
				MappedObject objectmapped,
				final int original_length,
				final boolean add_to_count
				) throws DatabaseException
			{
			assert(objectmapped.start<=objectmapped.end);
			assert(this.getResolution()>=LocusTreeDB.this.end_resolution);
			LOG.info("adding "+objectmapped+" in "+this.id+" level="+level+" resol:"+getResolution());
			
			
			//break object if needed
			
			//no need to break the object if it is the last level
			if(getResolution() >  LocusTreeDB.this.end_resolution)
				{
			
				List<MappedObject> splited=new ArrayList<MappedObject>(2);
				splited.add(objectmapped);
				while(true)
					{
					MappedObject last= splited.get(splited.size()-1);
					int idx1= childIndex(last.start);
					int idx2= childIndex(last.end);
					assert(idx1<=idx2);
					assert(isRoot()|| idx2<=   LocusTreeDB.this.child_per_node);
					
					if(idx1==idx2) break;
					
					MappedObject then= new MappedObject(last);
					
					then.start= this.start+ (idx1+1)*this.getChildResolution();
					if(then.start==last.end) break;
					LOG.info("spit to "+then);
					splited.add(then);
					last.end= then.start;
					}
				
				if(splited.size()!=1)
					{
					boolean is_first=true;//we just add the first chunk in the count
					for(MappedObject mo: splited)
						{
						LOG.info("adding splitted: "+mo+" "+mo.size());
						this.add(mo, original_length,is_first);
						is_first=false;
						}
					return;
					}
				}
			
			
			LOG.info("adding : "+objectmapped);
				
			
			if( 	(isRoot() && original_length > getChildResolution()) ||
					this.getResolution() <= LocusTreeDB.this.end_resolution ||
					original_length >  this.getResolution()
				)
				{
				LOG.info("Adding as feature");
				if(objectmapped.start<this.start)
					{
					System.err.println(objectmapped.toString()+" < root.start:"+this.start+" node_size:"+this.getResolution()+" level:"+this.level+" obj.length:"+original_length);
					}
				assert(objectmapped.start>=this.start);
				
				if(this.type2content==null)
					{
					this.type2content=new HashMap<Integer, ContentByType>();		
					}
				ContentByType content = this.type2content.get(objectmapped.typeId);
				if(content==null)
					{
					content=new ContentByType();
					this.type2content.put(objectmapped.typeId, content);
					}

				if(content.features==null) content.features=new java.util.HashSet<String>();
				content.features.add(objectmapped.name);	
				save(this);
				return;
				}
			
			
			
			final int child_index = childIndex(objectmapped.start);
			assert(isRoot() || child_index< child_per_node);
			LOG.info("Adding as child at index="+child_index+" level:"+level);
			LocusNode childNode=getChildAt(child_index);
			if(childNode==null)
				{
				childNode= new LocusNode();
				childNode.chromosome_id= this.chromosome_id;
				childNode.id= ++node_id_generator;
				childNode.start= this.start + child_index* this.getChildResolution();
				childNode.length=  this.getChildResolution();
				childNode.level=this.level+1;
				setChildAt(child_index, childNode);
				save(this);
				save(childNode);
				}
			
			if(add_to_count)
				{
				if(this.type2content==null)
					{
					this.type2content=new HashMap<Integer, ContentByType>();		
					}
				ContentByType content = this.type2content.get(objectmapped.typeId);
				if(content==null)
					{
					content=new ContentByType();
					this.type2content.put(objectmapped.typeId, content);
					}
				content.count++;
				//System.err.println(" type: "+objectmapped.typeId+" start:"+this.start+" length:"+this.length+" level:"+this.level+" count:"+content.count);
				save(this);
				}
			
			childNode.add(objectmapped,original_length,true);
			}
		
		private LocusNode getChildAt(int indexInList) throws DatabaseException
			{
			if(this.children_ids==null) return null;
			if(this.children_ids.size()<= indexInList) return null;
			Long id= this.children_ids.get(indexInList);
			return id==null?null:findNodeById(id);
			}
		
		private void setChildAt(int indexInList,LocusNode child)
			{
			assert(isRoot() || indexInList< LocusTreeDB.this.child_per_node);
				
			if(this.children_ids==null)
				{
				this.children_ids=new ArrayList<Long>(indexInList+1);
				}
			while(this.children_ids.size()<= indexInList)
				{
				this.children_ids.add(null);
				}
			this.children_ids.set(indexInList,child.id);
			}
		
		private int childIndex(int genome_pos)
			{
			if(genome_pos< this.start)
				{
				LOG.info(""+genome_pos+"<"+this.start+" level="+level);
				}
			assert(genome_pos>=this.start);
			int index=(genome_pos-this.start)/getChildResolution();
			LOG.info("index="+index+" start:"+this.start+" pos:"+genome_pos+" level="+level+" chidlRes:"+getChildResolution());
			assert(isRoot() || index<=LocusTreeDB.this.child_per_node);//<= car effet de bord avec 'end'
			return index;
			}
		
		private void scan(final Scanner scanner) throws DatabaseException
			{
			if(scanner==null || scanner.abort) return;
			
			
			if(scanner.end < this.start) return;
			
			if((this.start+this.length)<scanner.start) return;
			//System.err.println("C: level="+level+" length:"+length+" < resolution:"+scanner.resolution);
			
			if(this.length< scanner.resolution
				//|| getChildResolution()<= LocusTreeDB.this.end_resolution
				)
				{
				//System.err.println("D level:"+this.level);
				
				if(this.type2content!=null)
					{
					for(Integer typeid: this.type2content.keySet() )
						{
						
						if(scanner.abort) break;
						ContentByType ctb= this.type2content.get(typeid);
						Scanner.StatPoint pt= new Scanner.StatPoint();
						pt.start=this.start;
						pt.end=this.start+this.length;
						pt.count=ctb.count+(ctb.features!=null?ctb.features.size():0);
						//System.err.println(pt);
						
						if(pt.count<0) throw new RuntimeException();
						
						scanner.histogram(
								pt,
								typeid);
						}
					}
				return;
				}
			//System.err.println("D");
			if(this.type2content!=null)
				{
				for(Integer typeid: this.type2content.keySet() )
					{
					if(scanner.abort) return;
					ContentByType ctb= this.type2content.get(typeid);
					//System.out.println(" * type."+typeid+" "+ctb.count);
					if(ctb.features!=null)
						{
						for(String o:ctb.features)
							{
							TypeName tn= new TypeName(typeid, o);
							for(Position pos:name2mappedObject.get(txn, new TypeName(typeid, o)))
								{
								MappedObject mapped= new MappedObject(tn,pos);
								scanner.object(mapped);
								}
							}
						}
					}
				}
			//System.err.println("E");
			if(this.children_ids!=null )//&& this.getChildResolution()>=scanner.resolution)
				{
				for(int i=0; i< this.children_ids.size();++i)
					{
					if(scanner.abort) return;
					Long subid= this.children_ids.get(i);
					if(subid==null) continue;
					LocusNode child= findNodeById(subid);
					assert(child!=null);
					child.scan(scanner);
					}
				}
			}
			
		
		}
	
	private class LocusNodeBinding
	extends TupleBinding<LocusNode>
		{
		@Override
		public LocusNode entryToObject(TupleInput input)
			{
			LocusNode node= new LocusNode();
			node.id=input.readLong();
			node.chromosome_id=input.readInt();
			node.start=input.readInt();
			node.length=input.readInt();
			node.level=input.readInt();
			
			int n= input.readInt();
			if(n>0)
				{
				node.children_ids=new ArrayList<Long>(n);
				for(int i=0;i< n;++i)
					{
					boolean b=input.readBoolean();
					if(b)
						{
						node.children_ids.add(input.readLong());
						}
					else
						{
						node.children_ids.add(null);
						}
					}
				}
			n= input.readInt();
			if(n>0)
				{
				node.type2content=new HashMap<Integer, ContentByType>();
				for(int i=0;i< n;++i)
					{
					int typeId= input.readInt();
					ContentByType cbt= new ContentByType();
					cbt.count= input.readInt();
					int m= input.readInt();
					if(m>0)
						{
						cbt.features=new HashSet<String>();
						for(int j=0;j<m;++j)
							{
							cbt.features.add(input.readString());
							}
						}
					node.type2content.put(typeId, cbt);
					}
				}
			return node;
			}
		
		@Override
		public void objectToEntry(LocusNode node, TupleOutput output)
			{
			output.writeLong(node.id);
			output.writeInt(node.chromosome_id);
			output.writeInt(node.start);
			output.writeInt(node.length);
			output.writeInt(node.level);
			if(node.children_ids==null || node.children_ids.isEmpty())
				{
				output.writeInt(0);
				}
			else
				{
				output.writeInt(node.children_ids.size());
				for(int i=0;i< node.children_ids.size();++i)
					{
					Long child_id= node.children_ids.get(i);
					output.writeBoolean(child_id!=null);
					if(child_id!=null)
						{
						output.writeLong(child_id);
						}
					}
				}
			if(node.type2content==null || node.type2content.isEmpty())
				{
				output.writeInt(0);
				}
			else
				{
				output.writeInt(node.type2content.size());
				for(Integer typeId:node.type2content.keySet() )
					{
					ContentByType cbt= node.type2content.get(typeId);
					output.writeInt(typeId);
					output.writeInt(cbt.count);
					if(cbt.features==null || cbt.features.isEmpty())
						{
						output.writeInt(0);
						}
					else
						{
						output.writeInt(cbt.features.size());
						for(String s: cbt.features)
							{
							output.writeString(s);
							}
						}
					}
				}
			}
		}
	
	
	
	public void open() throws DatabaseException
		{
		EnvironmentConfig envConfig= new EnvironmentConfig();
		envConfig.setAllowCreate(true);
		envConfig.setReadOnly(false);
		this.environment= new Environment(dbHome, envConfig);
		DatabaseConfig cfg= new DatabaseConfig();
		cfg.setAllowCreate(true);
		cfg.setReadOnly(false);
		cfg.setTemporary(true);
		id2locusNode= new DefaultPrimaryDB<Long, LocusNode>(
			this.environment,txn,
			"id2locusNode",
			cfg,
			new LongBinding(),
			new LocusNodeBinding()
			);
		
		cfg= new DatabaseConfig();
		cfg.setAllowCreate(true);
		cfg.setReadOnly(false);
		cfg.setTemporary(true);
		featuresTypes= new DefaultPrimaryDB<String, Integer>(
				this.environment,txn,
				"featuresTypes",
				cfg,
				new StringBinding(),
				new IntegerBinding()
				);
		
		this.feature_type_generator=(int)featuresTypes.count();
		
		cfg= new DatabaseConfig();
		cfg.setAllowCreate(true);
		cfg.setReadOnly(false);
		cfg.setTemporary(true);
		name2chromosome=new DefaultPrimaryDB<String, ChromInfo>(
				this.environment,txn,
				"name2chromosome",
				cfg,
				new StringBinding(),
				new ChromInfoBinding()
				);
		this.chromosome_generator= (int)name2chromosome.count();
		
		this.name2mappedObject= new DefaultDuplicateDB<TypeName, Position>(
				this.environment,txn,
				"name2mapped",
				cfg,
				new TypeNameBinding(),
				new PositionBinding()
				);
		}
	
	public void close() throws DatabaseException
		{
		this.name2mappedObject.close();
		this.id2locusNode.close();
		this.featuresTypes.close();
		this.name2chromosome.close();
		this.environment.cleanLog();
		this.environment.close();
		}
	
	
	private LocusNode findNodeById(long id) throws DatabaseException
		{
		return id2locusNode.get(txn,id);
		}
	
	private void save(LocusNode n) throws DatabaseException
		{
		id2locusNode.put(txn,n.id, n);
		}
	
	public void insert(
		String type,
		String name,
		String chromosome,
		int start,
		int end
		) throws DatabaseException
		{
		MappedObject mapped= new MappedObject();
		mapped.start=start;
		mapped.end=end;
		mapped.name=name;
		
		//mapped.name+="(L="+mapped.size()+")";
		
		Integer type_id= featuresTypes.get(txn,type);
		if(type_id==null)
			{
			type_id=(++feature_type_generator);
			featuresTypes.put(txn,type,type_id);
			//assert(featuresTypes.count()>0L);
			}
		mapped.typeId=type_id;
		
		Long rootId=null;
		ChromInfo chromInfo = name2chromosome.get(txn,chromosome);
		if(chromInfo==null)
			{
			LOG.info("chromosome"+chromosome+" does not exist. Creating");
			chromInfo=new ChromInfo();
			chromInfo.name=chromosome;
			chromInfo.id= (++this.chromosome_generator);
			chromInfo.length=end;
			
			//create root LocusNode for this chromosome
			LocusNode root= new LocusNode();
			root.id= ++this.node_id_generator;
			root.chromosome_id=chromInfo.id;
			root.start=0;
			root.length=chromInfo.length;
			root.level=0;
			save(root);
			
			
			mapped.chromosome_id=root.chromosome_id;
			
			chromInfo.root_node_id=root.id;
			rootId=root.id;

			
			name2chromosome.put(txn,chromosome, chromInfo);
			}
		else
			{
			mapped.chromosome_id=chromInfo.id;
			rootId=chromInfo.root_node_id;
			if(chromInfo.length< end)
				{
				chromInfo.length=end;
				this.name2chromosome.put(txn, chromosome, chromInfo);
				}
			}
		LOG.info("chrom \""+chromosome+"\" root-id:"+rootId);
		assert(rootId!=null);
		LocusNode root= findNodeById(rootId);
		assert(root!=null);
		if(root.length< mapped.end)
			{
			root.length=mapped.end;
			save(root);
			}
		this.name2mappedObject.put(txn,
				new TypeName(mapped.typeId, mapped.name),
				new Position(mapped.chromosome_id, mapped.start, mapped.end)
				);
		
		root.add(mapped,mapped.size(),true);
		}
	
	private void dump(PrintStream out) throws DatabaseException
		{
		Walker<String,ChromInfo> w=this.name2chromosome.openWalker(txn);
		while(w.getNext()==OperationStatus.SUCCESS)
			{
			out.println("Chromosome: "+w.getKey());
			ChromInfo chromInfo= w.getValue();
			dump(out,"  ",findNodeById(chromInfo.root_node_id));
			}
		w.close();
		}
	
	private void dump(PrintStream out,String margin,LocusNode node) throws DatabaseException
		{
		out.println(margin+"+NodeID."+node.id+" [Level."+node.level+"] segment-id:= Id."+
				node.chromosome_id+":"+node.start+"-"+(node.start+node.length)+
				" segment-length:"+node.length);
		if(node.type2content!=null)
			{
			for(Integer typeid: node.type2content.keySet())
				{
				ContentByType cbt=node.type2content.get(typeid);
				out.println(margin+"    * count(type:"+typeid+") "+cbt.count);
				if(cbt.features!=null)
					{
					for(String o: cbt.features)
						{
						out.println(margin+"    *[type:"+typeid+"] "+o);
						}
					}
				}
			}
		if(node.children_ids!=null)
			{
			for(int i=0; i<node.children_ids.size();++i)
				{
				Long subid= node.children_ids.get(i);
				if(subid==null) continue;
				dump(out,margin+"  ",findNodeById(subid));
				}
			}
		}
	
	public void find(Scanner scanner)
		throws DatabaseException
		{
		ChromInfo ci=name2chromosome.get(txn,scanner.chromosome);
		if(ci==null) return;
		LocusNode node=findNodeById(ci.root_node_id);
		node.scan(scanner);
		}
	

	
	public Integer getChromosomeLength(String chromosome)
		throws DatabaseException
		{
		ChromInfo info=this.name2chromosome.get(txn, chromosome);
		return info==null?null:info.length;
		}
	
	
	//private HashMap<Long, LocusNode> id2locusNode= new HashMap<Long, LocusNode>();
	//private HashMap<String, ChromInfo> name2chromosome=new HashMap<String, ChromInfo>();
	//private Map<String,Integer> featuresTypes=new HashMap<String, Integer>();
	
	public void load(File f) throws IOException,DatabaseException
		{
		LOG.info("reading "+f);
		BufferedReader r= new BufferedReader(new FileReader(f));
		String line;
		while((line=r.readLine())!=null)
			{
			if(line.startsWith("#")) continue;
			
			
			String token[]=line.split("[\t]");
			//if(!token[0].equals("snp")) continue;
			LOG.info("inserting "+line);
			insert(token[0],token[1],token[2],
				Integer.parseInt(token[3]),
				Integer.parseInt(token[4])
				);
			
			}
		r.close();
		LOG.info("End reading :"+f);
		}
	
	public static void main(String[] args)
		{
		try
			{
			
			LOG.setLevel(Level.OFF);
			LocusTreeDB db= new LocusTreeDB();
			db.open();
			db.load(new File("/home/pierre/jeter.locusmap.xls"));
			db.dump(System.out);
			Scanner scanner= new Scanner();
			scanner.chromosome="chr2";
			scanner.start=3800500;
			scanner.end=3801310;
			scanner.resolution=100;
			db.find(scanner);
			db.close();
			} 
		catch(Throwable err)
			{
			err.printStackTrace();
			}
		}
	}
